<script lang="ts">
import { uniqueId, defaults } from 'lodash-es'
import { ref, reactive, defineComponent, type PropType, watch, inject } from 'vue'
import XIcon from '../x-icon/index.vue'

export interface UploadFile {
	uid: string
	url: string
	status: 'ready' | 'uploading' | 'success' | 'failed' | 'local'
	file?: File
	name: string
	percent: number
	errorMsg?: string
	[property: string]: any
}

export const createFileItem = (option?, uidPerfix = 'x-upload-file'): UploadFile =>
	reactive(
		defaults(option, {
			uid: uniqueId(`${uidPerfix}:`),
			url: '',
			status: 'ready',
			name: '',
			percent: 0,
		})
	)

export default defineComponent({
	name: 'x-upload-file',
})
</script>

<script setup lang="ts">
const props = defineProps({
	modelValue: {
		type: [Array, String],
		default: '',
	},
	/** 文件数量，0表示不限制 */
	count: {
		type: Number,
		default: 1,
	},
	accept: {
		type: String,
		default: '*',
	},
	/** 上传按钮旁边的提示文字 */
	tips: {
		type: String,
		default: '',
	},
	/**
	 * 文件上传的api
	 * 可通过 provide('x-upload-file:defaultApi') 全局提供默认值
	 */
	api: {
		type: Function as PropType<(file: UploadFile) => Promise<string>>,
	},
})

const emit = defineEmits(['update:modelValue'])

const file_list = ref<UploadFile[]>([])

const injectDefaultApi = inject('x-upload-file:defaultApi', undefined)

const setFileList = () => {
	if (props.count == 1) {
		if (typeof props.modelValue != 'string') {
			console.error(
				'[x-upload-file] props.modelValue except be "string" when props.count is 1, but got ' +
					typeof props.modelValue
			)
			return
		}
		file_list.value = props.modelValue ? [createFileItem({ url: props.modelValue, status: 'local' })] : []
	} else {
		if (!Array.isArray(props.modelValue)) {
			console.error(
				'[x-upload-file] props.modelValue except be "array" when props.count is not 1, but got ' +
					typeof props.modelValue
			)
			return
		}
		file_list.value = props.modelValue.map((it) => createFileItem({ url: it, status: 'local', percent: 100 }))
	}
}

const updateModelValue = () => {
	let value = '' as any
	if (props.count == 1) {
		value = file_list.value[0]?.url ?? ''
	} else {
		value = file_list.value.map((it) => it.url)
	}
	emit('update:modelValue', value)
}

const handleUpload = (file: File) => {
	const fileItem = createFileItem({ status: 'ready', file, name: file.name })
	file_list.value.push(fileItem)

	const upload = props.api ?? injectDefaultApi
	if (upload)
		upload(fileItem)
			.then((url) => {
				fileItem.url = url
				fileItem.percent = 100
				fileItem.status = 'success'
			})
			.catch((e) => {
				fileItem.status = 'failed'
				fileItem.errorMsg = e.message
				deleteFile(file_list.value.findIndex((it) => it.uid == fileItem.uid))
			})
			.finally(() => {
				updateModelValue()
			})
	return false
}

const moveFileItem = (index: number, step: number) => {
	if (!file_list.value[index + step]) {
		return
	}
	let old = file_list.value[index]
	file_list.value[index] = file_list.value[index + step]
	file_list.value[index + step] = old

	updateModelValue()
}

const deleteFile = (index) => {
	if (!file_list.value[index]) return
	file_list.value.splice(index, 1)
	updateModelValue()
}

const calcProgressStatus = (file: UploadFile) => {
	if (file.status == 'uploading') {
		return ''
	} else if (file.status == 'failed') {
		return 'exception'
	} else if (['local', 'success'].includes(file.status)) {
		return 'success'
	}
	return ''
}
const calcFileShowName = (file: UploadFile) => {
	if (file.name) return file.name
	if (file.url) {
		let split = file.url.split('/')
		return split[split.length - 1]
	}
	return 'unknown file'
}

// init流程
watch(
	() => props.modelValue,
	() => setFileList(),
	{ deep: true, immediate: true }
)

defineExpose({ deleteFile })
</script>

<template>
	<div class="x-upload-file space-y-1">
		<div class="flex items-center space-x-2">
			<el-upload
				v-if="count == 0 || file_list.length < count"
				class="flex flex-shrink-0"
				:accept="accept"
				action=""
				:before-upload="handleUpload"
				:multiple="count != 1"
			>
				<slot>
					<el-button type="primary">点击上传文件</el-button>
				</slot>
			</el-upload>
			<slot name="tips">
				<div class="text-xs text-gray-500">{{ tips }}</div>
			</slot>
		</div>
		<div
			class="rounded p-2 text-sm leading-snug hover:bg-gray-100"
			v-for="(it, index) in file_list"
			:key="it.uid"
			@mouseenter="it._hover = true"
			@mouseleave="it._hover = false"
		>
			<div class="flex">
				<el-tooltip placement="left" popper-class="tooltip-popper">
					<div class="flex flex-grow items-center space-x-1 overflow-hidden">
						<XIcon eicon="Document" :size="16" />
						<div class="line-clamp-1 break-all">{{ calcFileShowName(it) }}</div>
					</div>
					<template #content>
						<div style="max-width: 200px">{{ it.name ? it.name : it.url }}</div>
					</template>
				</el-tooltip>
				<div v-show="it._hover" class="flex flex-shrink-0 items-center space-x-2">
					<XIcon
						class="cursor-pointer"
						eicon="Top"
						:size="18"
						:color="it._hover ? '#409eff' : '#464646'"
						@click="moveFileItem(index, -1)"
					/>
					<XIcon
						class="cursor-pointer"
						eicon="Bottom"
						:size="18"
						:color="it._hover ? '#409eff' : '#464646'"
						@click="moveFileItem(index, 1)"
					/>
					<XIcon
						class="cursor-pointer"
						eicon="Delete"
						:size="18"
						:color="it._hover ? '#b31117' : '#464646'"
						@click="deleteFile(index)"
					/>
				</div>
			</div>
			<el-progress
				v-show="it.status != 'local' && it.status != 'success'"
				class="mt-1"
				:stroke-width="2"
				:percentage="it.percent"
				:status="calcProgressStatus(it)"
			/>
		</div>
	</div>
</template>
